#include "scheduledialog.h"
#include "ui_scheduledialog.h"
#include <QMessageBox>
#include <QNetworkInterface>
#include <QTcpServer>
#include <iostream>
#include <QString>
#include "schedulepopulation.h"
#include "scheduleindividual.h"
#include "scheduleconflicts.h"

ScheduleDialog::ScheduleDialog(QWidget *parent) :
        QDialog(parent), ui(new Ui::ScheduleDialog)
{
    ui->setupUi(this);
    ui->SendMsgBtn->setEnabled(false);
    server = new QTcpServer();

    connect(server, SIGNAL(newConnection()), this, SLOT(connectClient()), Qt::QueuedConnection);

    //for now, just set up the classes and difficulty straight up
    //Later, could be interesting to have the user choose when they start the server

    populationSize = 20;
    timeSlots = 2;

    numClasses = 40;
    difficulty = .3;

    sc = new ScheduleConflicts(numClasses);
    sc->generateRandomly(numClasses, difficulty);

    responses = QVector<QString>();
}

ScheduleDialog::~ScheduleDialog()
{
    delete ui;
    delete sc;
    for (int i = 0; i < clients.size(); i++){
        delete clients.at(i);
    }
}

void ScheduleDialog::changeEvent(QEvent *e)
{
    QDialog::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

void ScheduleDialog::on_StartServerBtn_clicked()
{
    QString ipAddress = QHostAddress(QHostAddress::LocalHost).toString();

    if (!server->listen()) {
        QMessageBox::critical(this, "Server Error",
                              "Could not start server!");
        return;
    }

    //Figures out what this machine's IP address is
    QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
    for (int i=0; i<addresses.size(); i++) {
        if (addresses.at(i) != QHostAddress::LocalHost &&
            addresses.at(i).toIPv4Address()) {
            ipAddress = addresses.at(i).toString();
            break;
        }
    }


    ui->MainTxt->append("IP Address: " + ipAddress);
    ui->MainTxt->append("Port: " + QString::number(server->serverPort()));
}

// When a client connects, set up bookkeeping for it
void ScheduleDialog::connectClient() {
    QTcpSocket* socket = server->nextPendingConnection();

    Client* client = new Client(socket);

    connect(client, SIGNAL(gotData(QString)), this, SLOT(getData(QString)), Qt::QueuedConnection);

    clients.push_back(client);

    if (clients.size() == 2){
        ui->SendMsgBtn->setEnabled(true);
        ui->MainTxt->append("Two clients connected!");
    }
}

void ScheduleDialog::getData(QString message){
    //Want to evolve a few more times here, and print to the console

    responses.push_back(message);

    QString bigPop = "";

    QString str;
    str.setNum(responses.size());

    if (responses.size() == clients.size()){

        for (int i = 0; i < responses.size(); i++){
            bigPop += responses.at(i);

            SchedulePopulation* sPop = unpackPopulation(bigPop);

            int genNumber = 0;
            double bestFitness = sPop->getBestFitness();
            string bestGenes = sPop->getBestGenes();

            QString numString;
            numString.setNum(0);

            ui->MainTxt->append("Generation: " + numString);
            ui->MainTxt->append(QString::fromStdString("Best Genes: " + bestGenes));

            numString.setNum(bestFitness);
            ui->MainTxt->append("BestFitness: " + numString);

            for (int j = 0; j < 500; j++){

                genNumber++;
                SchedulePopulation* nextGen = sPop->reproduce();
                delete sPop;
                sPop = nextGen;

                double fitness = sPop->getBestFitness();
                string genes = sPop->getBestGenes();

                if (fitness > bestFitness){
                    bestFitness = fitness;
                    bestGenes = genes;
                }

                if (genNumber % 10 == 0){
                    numString.setNum(genNumber);

                    ui->MainTxt->append("Generation: " + numString);
                    ui->MainTxt->append(QString::fromStdString("Best Genes: " + bestGenes));

                    numString.setNum(bestFitness);
                    ui->MainTxt->append("BestFitness: " + numString);
                    ui->MainTxt->append("\n");
                    ui->MainTxt->repaint();
                }
            }
        }
    }

}

SchedulePopulation* ScheduleDialog::unpackPopulation(QString str){
    //NEED TO PUT THE COMPLETED INDIVIDUALS SOMEWHERE
    QVector<ScheduleIndividual*>* vec = new QVector<ScheduleIndividual*>();

    //This gives a list of individuals, separated by whitespace
    QStringList indivList = str.split(":", QString::SkipEmptyParts);

    int indivsLength;

    for (int i = 0; i < indivList.size(); i++){
        //This gets one particular individual
        QString oneIndiv = indivList.at(i);
        QStringList indivIntsList = oneIndiv.split(" ", QString::SkipEmptyParts);

        //Need to get this in order to use for size when initializing pop
        indivsLength = indivIntsList.size();

        ScheduleIndividual* id = new ScheduleIndividual(indivIntsList.size(), sc, timeSlots);
        id->populateFromList(indivIntsList);

        vec->push_back(id);
    }

    SchedulePopulation* sPop = new SchedulePopulation(indivList.size(), indivsLength, sc, timeSlots);
    sPop->populateFromVector(vec);

    return sPop;
}

void ScheduleDialog::on_SendMsgBtn_clicked()
{
    ui->MainTxt->append("Evolving initial population!");

    QString conflicts = packConflicts();

    //need to send schedule conflicts to everyone
    for (int i = 0; i < clients.size(); i++){
        clients[i]->sendConflicts(conflicts);
    }

    //Send the number of timeslots
    QString times;
    times.setNum(timeSlots);

    for (int i = 0; i < clients.size(); i++){
        clients[i]->sendTimeSlots(times);
    }

    // create a population
    SchedulePopulation* sp = new SchedulePopulation(populationSize, numClasses, sc, timeSlots);
    sp->populateRandomly();

    //do a few evolution steps here

    int genNumber = 0;
    QString numString;
    double bestFitness = sp->getBestFitness();
    string bestGenes = sp->getBestGenes();

    for (int i = 0; i < 100; i++){
        SchedulePopulation* nextGen = sp->reproduce();
        delete sp;
        sp = nextGen;

        double fitness = sp->getBestFitness();
        string genes = sp->getBestGenes();

        if (fitness > bestFitness){
            bestFitness = fitness;
            bestGenes = genes;
        }

        if (genNumber % 10 == 0){
            numString.setNum(genNumber);

            ui->MainTxt->append("Generation: " + numString);
            ui->MainTxt->append(QString::fromStdString("Best Genes: " + bestGenes));

            numString.setNum(bestFitness);
            ui->MainTxt->append("BestFitness: " + numString);
            ui->MainTxt->append("\n");
            ui->MainTxt->repaint();
        }
        genNumber++;
    }

    // send half to each client

    for (int i = 0; i < clients.size(); i++){

       //want to pick apart the population from i*popsize/i to i+1 * popsize/i
        QVector<ScheduleIndividual*>* vec = new QVector<ScheduleIndividual*>();
        for (int j = i*(sp->getPopSize()/clients.size()); j < (i+1)*(sp->getPopSize()/clients.size()); j++){
            vec->push_back(sp->sPop->at(j));
        }

        SchedulePopulation* p = new SchedulePopulation(sp->getPopSize()/clients.size(), numClasses, sc, timeSlots);
        p->populateFromVector(vec);

        QString population = packPopulation(p);
        clients[i]->sendPopulation(population);

        QString string;
        string.setNum(i);
        ui->MainTxt->append("Sending info to Client " + string);
    }
    ui->MainTxt->append("\n");

//    //For now, just send copies to both
//    QString population = packPopulation(sp);
//
//    for (int i = 0; i < clients.size(); i++){
//        clients[i]->sendPopulation(population);
//    }
}
QString ScheduleDialog::packPopulation(SchedulePopulation* sp){

    //put all of it into a qstring of ints that are individuals
    QString popString = "";

    for (int i = 0; i < sp->getPopSize(); i++){
        for (int j = 0; j < sp->getIndivSize(); j++){
            int num = sp->sPop->at(i)->myVec->at(j);
            QString str;
            str.setNum(num);
            popString += str;
            popString += " ";
        }
        popString += ":";
    }

    return popString;
}

QString ScheduleDialog::packConflicts(){

    QString conString = "";

    //run through conflicts array, put into string
    for (int i = 0; i < numClasses; i++){
        for (int j = 0; j < numClasses; j++){
            QString str;
            str.setNum(sc->conflicts[i][j]);
            conString += str;
            conString += " ";
        }
        conString += ":";
    }

    //Return packed string
    return conString;
}
